//=========================================================================
// Copyright (C) 2022 The C++ Component Model(COMO) Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//=========================================================================

/*
class CMemPool
+--------------+    m_pMemBlock___      /----------------\
| CMemPool_Unit +--> _Unit 0 <==>  _Unit 1   _Unit 2 ...  _Unit N
+--------------+          m_pFreeMemBlock---^

class CMemPoolSet
+------------------------+
| CMemPool_Unit UnitSize0 | --> CMemPool0
| CMemPool_Unit UnitSize1 | --> CMemPool1
| ...                    | --> ...
| ...                    | --> ...
| CMemPool_Unit UnitSizeN | --> CMemPoolN
+------------------------+
| CMemPool_Unit UnitSize0 | -->Gereral Purpose fix sized <= from g_MemPool
| CMemPool_Unit UnitSize1 | -->Gereral Purpose fix sized <= from g_MemPool
| ...................... |
|                        |
+------------------------+
*/

#include <malloc.h>
#include <stdint.h>
#include "MemPool.h"

// Provided by other programs
//CMemPoolSet *cmemPoolSet = NULL;

/**
 * Constructor of this class. It allocate memory block from system and create
 * a static double linked list to manage all memory unit.
 *
 * Parameters:
 *     [in] ulUnitNum     The number of unit which is a part of memory block.
 *     [in] ulUnitSize The size of unit.
 */
CMemPool *CMemPool_New()
{
    CMemPool *this = malloc(sizeof(CMemPool));
    if (NULL == this) {
        return NULL;
    }

    this->m_pMemBlock = NULL;
    this->m_pAllocatedMemBlock = NULL;
    this->m_pFreeMemBlock = NULL;
    this->m_ulBlockSize = 0;
    this->m_ulUnitSize = 0;
    return this;
}

CMemPool *CMemPool_New2(void *buffer, size_t ulUnitNum, size_t ulUnitSize)
{
    CMemPool *this = malloc(sizeof(CMemPool));
    if (NULL == this) {
        return NULL;
    }

    this->m_pAllocatedMemBlock = NULL;
    this->m_pFreeMemBlock = NULL;
    CMemPool_SetBuffer(this, buffer, ulUnitNum, ulUnitSize);
    return this;
}

/**
 * Destructor of this class. Its task is to free memory block.
 */
void CMemPool_Delete(CMemPool *this)
{
    if (this->shouldFreeMemBlock && (NULL != this->m_pMemBlock))  {
        free(this->m_pMemBlock);
    }
}

/**
 * SetBuffer
 */
bool CMemPool_SetBuffer(CMemPool *this, void *buffer, size_t ulUnitNum, size_t ulUnitSize)
{
    this->m_ulUnitSize = ALIGN8(ulUnitSize);
    this->m_ulBlockSize = ulUnitNum * (this->m_ulUnitSize + sizeof(CMemPool_Unit));

    if (NULL == buffer) {
        this->m_pMemBlock = malloc(this->m_ulBlockSize);
        if (NULL == this->m_pMemBlock) {
            return false;
        }

        this->shouldFreeMemBlock = true;
    }
    else {
        this->m_pMemBlock = buffer;
        this->shouldFreeMemBlock = false;
    }

    if (NULL != this->m_pMemBlock) {
        // Link all mem unit.

        size_t ul = this->m_ulUnitSize + sizeof(CMemPool_Unit);
        size_t ulUnitNum = this->m_ulBlockSize / ul;
        for (size_t i = 0;  i < ulUnitNum;  i++) {
            CMemPool_Unit *pCurUnit =
                        (CMemPool_Unit *)((char *)this->m_pMemBlock + (i * ul));

            // Insert the new unit at head.
            pCurUnit->pPrev = NULL;
            pCurUnit->pNext = this->m_pFreeMemBlock;

            if (NULL != this->m_pFreeMemBlock) {
                this->m_pFreeMemBlock->pPrev = pCurUnit;
            }
            this->m_pFreeMemBlock = pCurUnit;
        }
    }

    return true;
}

/**
 * Alloc
 * To allocate a memory unit. If memory pool can`t provide proper memory unit,
 * will call system function.
 *
 * Parameters:
 *     [in] ulSize       	  Memory unit size.
 *     [in] iTryToUseMemPool  Whether use memory pool.
 *
 * Return Values:
 *     Return a pointer to a memory unit.
 */
void *CMemPool_Alloc(CMemPool *this, size_t ulSize, TryToUseMemPool iTryToUseMemPool)
{
    if ((ulSize > this->m_ulUnitSize) ||
               (NULL == this->m_pMemBlock) || (NULL == this->m_pFreeMemBlock)) {
        if (MUST_USE_MEM_POOL == iTryToUseMemPool) {
        	return NULL;
        }

    	return malloc(ulSize);
    }

    // Now FreeList isn`t empty
    CMemPool_Unit *pCurUnit = this->m_pFreeMemBlock;

    // Get a unit from free linkedlist.
    this->m_pFreeMemBlock = pCurUnit->pNext;
    if (NULL != this->m_pFreeMemBlock) {
        this->m_pFreeMemBlock->pPrev = NULL;
    }

    pCurUnit->pNext = this->m_pAllocatedMemBlock;

    if (NULL != this->m_pAllocatedMemBlock) {
        this->m_pAllocatedMemBlock->pPrev = pCurUnit;
    }
    this->m_pAllocatedMemBlock = pCurUnit;

    return (void *)((char *)pCurUnit + sizeof(CMemPool_Unit));
}

/**
 * To free a memory unit. If the pointer of parameter point to a memory unit,
 * then insert it to "Free linked list". Otherwise, call system function "free".
 *
 * Parameters:
 *     [in] p    point to a memory unit and prepare to free it.
 *
 * Return Values:
 *     none
 */
void CMemPool_Free(CMemPool *this, void *p)
{
    if ((this->m_pMemBlock < p)
                && (p < (void *)((char *)this->m_pMemBlock + this->m_ulBlockSize))) {
        CMemPool_Unit *pCurUnit = (CMemPool_Unit *)((char *)p - sizeof(CMemPool_Unit));

        this->m_pAllocatedMemBlock = pCurUnit->pNext;
        if (NULL != this->m_pAllocatedMemBlock) {
            this->m_pAllocatedMemBlock->pPrev = NULL;
        }

        pCurUnit->pNext = this->m_pFreeMemBlock;
        if (NULL != this->m_pFreeMemBlock) {
            this->m_pFreeMemBlock->pPrev = pCurUnit;
        }

        this->m_pFreeMemBlock = pCurUnit;
    }
    else {
        free(p);
    }
}

/**
 * Parameters:
 *     [in] p    point to a memory unit.
 *
 * Return Values:
 *     whether p in this CMemPool
 */
bool CMemPool_CheckExist(CMemPool *this, void *p)
{
    if ((this->m_pMemBlock < p) &&
              (p < (void *)((char *)this->m_pMemBlock + this->m_ulBlockSize))) {
        return true;
    }
    return false;
}

/**
 * CheckFull
 * Determine if the pool is full.
 *
 * Return Values:
 *     Return true if the pool is full.
 */
bool CMemPool_CheckFull(CMemPool *this)
{
    return (NULL == this->m_pMemBlock) || (NULL == this->m_pFreeMemBlock);
}

/**
 * (m_MemPoolSet == NULL) indicates the construction fail.
 * memPoolItems must be ordered by keyword lUnitSize.
 */
CMemPoolSet *CMemPoolSet_New(MemPoolItem *memPoolItems, size_t num)
{
    CMemPoolSet *this = malloc(sizeof(CMemPoolSet));
    if (NULL == this) {
        return NULL;
    }

    this->g_MemPool = NULL;
    this->m_g_lUnitNum = 0;
    this->m_g_lUnitSize = 0;

    this->m_MemPoolSet = (MemPoolItem *)calloc(num, sizeof(MemPoolItem));
    if (NULL != this->m_MemPoolSet) {
        for (size_t i = 0;  i < num;  i++) {
            this->m_MemPoolSet[i].memPool = CMemPool_New2(NULL,
                                                     memPoolItems[i].lUnitNum,
                                                     memPoolItems[i].lUnitSize);
            if (NULL == this->m_MemPoolSet[i].memPool) {
                for (size_t j = 0;  j < i;  j++) {
                    free(this->m_MemPoolSet[i].memPool);
                }
                free(this->m_MemPoolSet);
                free(this);
                return NULL;
            }

            this->m_MemPoolSet[i].lUnitNum = memPoolItems[i].lUnitNum;
            this->m_MemPoolSet[i].lUnitSize = memPoolItems[i].lUnitSize;
        }
    }
    this->m_ItemNum = num;

    return this;
}

CMemPoolSet *CMemPoolSet_New2(MemPoolItem *memPoolItems, size_t num,
                                          size_t g_lUnitNum, size_t g_lUnitSize)
{
    CMemPoolSet *this = CMemPoolSet_New(memPoolItems, num);
    if (NULL == this) {
        return NULL;
    }

    if (! CMemPoolSet_CreateGeneralMemPool(this, g_lUnitNum, g_lUnitSize)) {
        return NULL;
    }

    return this;
}

bool CMemPoolSet_CreateGeneralMemPool(CMemPoolSet *this, size_t lUnitNum,
                                                               size_t lUnitSize)
{
    if ((0 == lUnitNum ) || (0 == lUnitSize)) {
        return false;
    }

    this->g_MemPool = CMemPool_New();
    if (NULL == this->g_MemPool) {
        return false;
    }

    this->m_g_lUnitNum = lUnitNum;
    this->m_g_lUnitSize = lUnitSize;

    this->m_g_MemPoolSet = (MemPoolItem *)calloc(lUnitNum, sizeof(MemPoolItem));
    if (NULL == this->m_g_MemPoolSet) {
        free(this->g_MemPool);
        return false;
    }

    for (size_t i = 0;  i < lUnitNum;  i++) {
        this->m_g_MemPoolSet[i].memPool = CMemPool_New();
        if (NULL == this->m_g_MemPoolSet[i].memPool) {
            for (size_t j = 0;  j < i;  j++) {
                free(this->m_g_MemPoolSet[j].memPool);
            }

            free(this->g_MemPool);
            this->g_MemPool = NULL;
            free(this->m_g_MemPoolSet);
            this->m_g_MemPoolSet = NULL;
            return false;
        }
    }

    return true;
}

/**
 * ~CMemPoolSet()
 */
void CMemPoolSet_Delete(CMemPoolSet *this)
{
    for (size_t i = 0;  i < this->m_ItemNum;  i++) {
        free(this->m_MemPoolSet[i].memPool);
    }
    free(this->m_MemPoolSet);
}

/**
 * Alloc
 * To allocate a memory unit.
 *
 * Parameters:
 *     [in] ulSize            Memory unit size.
 *     [in] iTryToUseMemPool  Whether use memory pool.
 *
 * Return Values:
 *     Return a pointer to a memory unit.
 */
void *CMemPoolSet_Alloc(CMemPoolSet *this, size_t ulSize,
                                               TryToUseMemPool iTryToUseMemPool)
{
    void *p;
    size_t i;
    size_t ulElemSize;

    for (i = 0;  i < this->m_ItemNum;  i++) {
        if (ulSize <= this->m_MemPoolSet[i].lUnitSize) {
            p = CMemPool_Alloc(this->m_MemPoolSet[i].memPool, ulSize,
                                                              iTryToUseMemPool);
            if (NULL == p) {
                break;
            }
            else {
                return p;
            }
        }
    }

    if (i < this->m_ItemNum) {
        ulElemSize = this->m_MemPoolSet[i].lUnitSize;
    }
    else {
        ulElemSize = ulSize;
    }

    // acquire memory from g_MemPool
    size_t lastUnitSize = UINTMAX_MAX;
    size_t pos = this->m_g_lUnitNum;
    if (NULL != this->g_MemPool) {

/*This algorithm needs to be optimized！
Gereral Purpose, fix sized, from g_MemPool

             +------------------------+
           0 |                        |
             | === === === === ===    |-->SetBuffer, m_ulBlockSize, m_ulUnitSize
             | ......                 |
m_g_lUnitNum |                        |
             +------------------------+
*/
        for (i = 0;  i < this->m_g_lUnitNum;  i++) {
            if ((ulSize <= this->m_g_MemPoolSet[i].lUnitSize) &&
                        (this->m_g_MemPoolSet[i].lUnitSize < lastUnitSize) &&
                        (! CMemPool_CheckFull(this->m_g_MemPoolSet[i].memPool))) {
                lastUnitSize = this->m_g_MemPoolSet[i].lUnitSize;
                pos = i;
            }
        }

        if (pos < this->m_g_lUnitNum) {
            p = CMemPool_Alloc(this->m_g_MemPoolSet[pos].memPool,
                                                      ulSize, iTryToUseMemPool);
            if (NULL != p) {
                return p;
            }
        }

        for (i = 0;  i < this->m_g_lUnitNum;  i++) {
            CMemPool *tmpPool = this->m_g_MemPoolSet[i].memPool;
            if (0 == tmpPool->m_ulBlockSize) {
                void *mem = CMemPool_Alloc(this->g_MemPool,
                                        this->m_g_lUnitSize, MUST_USE_MEM_POOL);
                if (NULL == mem)
                    return NULL;

                CMemPool_SetBuffer(tmpPool, mem, ulElemSize,
                                                this->m_g_lUnitSize/ulElemSize);

                return CMemPool_Alloc(tmpPool, ulSize, iTryToUseMemPool);
            }
        }
    }

    return NULL;
}

/**
 * To free a memory unit. If the pointer of parameter point to a memory unit,
 * then insert it to "Free linked list". Otherwise, do nothing.
 *
 * Parameters:
 *     [in] p    point to a memory unit and prepare to free it.
 *
 * Return Values:
 *     whether memory P is freed.
 */
bool CMemPoolSet_Free(CMemPoolSet *this, void *p)
{
    for (size_t i = 0;  i < this->m_ItemNum;  i++) {
        if (CMemPool_CheckExist(this->m_MemPoolSet[i].memPool, p)) {
            CMemPool_Free(this->m_MemPoolSet[i].memPool, p);
            return true;
        }
    }

    return false;
}
